package com.jzo2o.market.task;

import com.jzo2o.api.customer.CommonUserApi;
import com.jzo2o.api.customer.dto.response.CommonUserResDTO;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.market.constants.RedisConstants;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.redis.model.SyncMessage;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 同步数据的异步处理类
 *
 * @Author mr.wu
 * @Date 2024-12-20 15:01
 */
@Component
@Slf4j
public class SyncMessageTask {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 批量获取缓存同步数据
     * 将同步数据同步到DB
     *
     * @param index 索引号的值，就是 活动id%10的结果
     * @return
     */
    @Async("taskExecutor")
    public void batchHandeSyncMessage(Long index) {
        //执行任务
        log.info("{}执行任务:{}", Thread.currentThread().getName(), index);
        List<SyncMessage<Object>> syncMessageList = null;
        String redisKey = null;
        RLock lock = null;
        try {
            //1.创建redisson实例
            redisKey = String.format(RedisConstants.RedisKey.COUPON_SEIZE_SYNC_QUEUE_NAME, index);
            lock = redissonClient.getLock("LOCK" + redisKey);
            //2.队列名尝试进行加锁
            boolean result = lock.tryLock(3, -1, TimeUnit.SECONDS);
            if (!result) {
                //3.加锁失败，拒绝业务
                log.error("线程：{}加锁失败，", Thread.currentThread().getName());
                return;
            } else {
                //4.加锁成功，执行业务
                syncMessageList = this.batchGetSyncMessage(redisKey);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("加锁异常", e);
        } finally {
            if (CollUtils.isNotEmpty(syncMessageList)) {
                //减少库存&&添加coupon数据
                SyncMessageTask owner = applicationContext.getBean(SyncMessageTask.class);
                owner.syncMessageToDB(syncMessageList);
                //从同步队列缓存中删除处理完的批量数据
                List<String> hashKeyList = syncMessageList.stream().map(SyncMessage::getKey).collect(Collectors.toList());
                redisTemplate.opsForHash().delete(redisKey, hashKeyList.toArray());
            }
            //释放锁
            lock.unlock();
        }
    }

    @Autowired
    private CommonUserApi commonUserApi;
    @Autowired
    private IActivityService activityService;
    @Autowired
    private ICouponService couponService;

    @Transactional(rollbackFor = Exception.class)
    public void syncMessageToDB(List<SyncMessage<Object>> syncMessageList) {
        for (SyncMessage<Object> syncMessage : syncMessageList) {
            //1.获取用户信息
            Long userId = Long.valueOf(syncMessage.getKey());
            CommonUserResDTO user = commonUserApi.findById(userId);
            if (user == null) {
                throw new CommonException("用户不存在");
            }
            //2.获取活动信息
            Long activityId = Long.valueOf(syncMessage.getValue().toString());
            Activity activity = activityService.getById(activityId);
            if (activity == null) {
                throw new CommonException("活动不存在");
            }
            //3.添加到coupon表中
            Coupon coupon = new Coupon();
            //设置用户信息
            coupon.setUserId(userId);
            coupon.setUserName(user.getNickname());
            coupon.setUserPhone(user.getPhone());
            //设置活动信息
            coupon.setName(activity.getName());
            coupon.setActivityId(activityId);
            coupon.setAmountCondition(activity.getAmountCondition());
            coupon.setDiscountAmount(activity.getDiscountAmount());
            coupon.setDiscountRate(activity.getDiscountRate());
            coupon.setStatus(CouponStatusEnum.NO_USE.getStatus());
            coupon.setType(activity.getType());
            coupon.setValidityTime(DateUtils.now().plusDays(activity.getValidityDays()));//过期时间点

            boolean resultForCoupon = couponService.save(coupon);
            if (!resultForCoupon) {
                throw new DBException("新增优惠券失败");
            }

            //4.减少库存
            boolean resultForActivity = activityService.decrStockNum(activityId);
            if (!resultForActivity) {
                throw new DBException("更新活动库存失败");
            }
        }
    }


    /**
     * 批量获取同步数据
     */
    @Autowired
    private RedisTemplate redisTemplate;

    public List<SyncMessage<Object>> batchGetSyncMessage(String redisKey) {
        Cursor<Map.Entry<String, Object>> cursor = null;
        try {
            //通过游标方式批量获取Hash中的游标对象
            cursor = redisTemplate.opsForHash().scan(redisKey, ScanOptions.scanOptions().count(10).build());
            if (cursor != null) {
                //解析游标对象中的数据
                List<SyncMessage<Object>> syncMessageList = cursor.stream().map(x -> SyncMessage.builder().key(x.getKey().toString()).value(x.getValue()).build())
                        .collect(Collectors.toList());
                return syncMessageList;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //释放游标资源
            cursor.close();
        }
        return null;
    }
}